import java.io.FileInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Properties;
import java.util.concurrent.Semaphore;


public class ServerController {

	public static int PORT_NUMBER = 6699;
	public static int gridWidth = 6;
	public static int gridHeight = 8;
	public static int maxPlayers  = 0;
	public static int playerTime = 0;
	public static boolean registerWithMaster = false;
	public static Properties config;

	private static GameBoard gb = null;
	private static GameLogic gl = new GameLogic();
	private static ArrayList<ClientThread> clients = new ArrayList<ClientThread>();
	private static Semaphore broadcast = new Semaphore(1);

	private static boolean gameWon = false;
	private static int wonId = 0;
	private static boolean gameStalemate = false;
	private static boolean wasChat = false;
	private static boolean disconnect = false;
	private static boolean ready = false;
	private static int dcdId = 0;
	private static String chat = "";
	private static GamePiece piece = null;
	private static Player player = null;

	static ServerSocket incoming = null;
	private static int readyCount = 0;


	//thread that runs the accept loop
	public static class ServerThread extends Thread {
		public void run()
		{
			//server accept loop
			while(true)
			{
				Socket client = null;
				try {
					client = incoming.accept();
				} catch (IOException e) {
					System.err.println("Failed to accept client connection, terminating");
					return;
				}
				if(maxPlayers != 0 && maxPlayers <= clients.size() || isGameInProgress())
				{
					try {
						client.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
					continue;
				}
				ClientThread c = new ServerController.ClientThread(client);
				clients.add(c);
				c.start();
			}
		}
	}

	///Thread that broadcasts messages to all clients
	private static class LogicThread extends Thread {
		public void run()
		{
			while(true)
			{
				try {
					broadcast.acquire();
				} catch (InterruptedException e) {
					e.printStackTrace();
					continue;
				}
				for(Iterator<ClientThread> it = clients.iterator(); it.hasNext();)
				{
					ClientThread c = it.next();
					if(gameWon || gameStalemate)
						c.proto.sendReset();
					if(gameWon)
						c.proto.sendWon(wonId);
					else if(gameStalemate)
						c.proto.sendStalemate();
					if(piece != null)
						c.proto.sendMove(piece);
					if(player != null)
						c.proto.sendPlayer(player);
					if(wasChat)
						c.proto.sendChat(chat);
					if(disconnect)
						c.proto.sendDisconnect(dcdId);
					if(ready && readyCount <= gl.playerList.size())
					{
						c.proto.sendReady();
					}
				}
				if(piece != null && !gameWon && !gameStalemate)
				{
					gl.nextTurn();
					for(Iterator<ClientThread> it = clients.iterator(); it.hasNext();)
					{
						ClientThread c = it.next();
						c.proto.sendTurnRotate();
					}
				}
				if(gameWon || gameStalemate || (disconnect && gl.playerList.size() == 0))
				{
					gameWon = false;
					gameStalemate = false;
					readyCount = 0;
					gb.reset();
					gl.reset();
					for(Iterator<ClientThread> it = clients.iterator(); it.hasNext();)
						it.next().ready = false;
				}

				piece = null;
				player = null;
				wasChat = false;
				disconnect = false;
				chat = "";
			}
		}
	}
	public static class ClientThread extends Thread {
		Socket client = null;
		public Protocol proto = null;
		int index = 0;
		public boolean ready = false;
		public boolean active = false;
		public ClientThread(Socket client) {
			this.client = client;
		}

		public void run()
		{
			try {
				proto = new Protocol(client, gl, gb);
				proto.setServer();
			} catch (IOException e) {
				System.err.println("failed to create protocol object: " + e.getMessage());
				return;
			}
			GamePiece p = null;
			
			active = true;
			while(proto.readProtoMessage())
			{
				if(proto.last.equals(Protocol.MESSAGE_TYPES.MOVE) && (p = proto.getPiece()) != null)
				{
					piece = p;
					gameWon = GameLogic.checkForWin(gb, p.x, p.y);
					if(gameWon)
						wonId = proto.getPlayerId();
					gameStalemate = GameLogic.checkForStalemate(gb);
					broadcast.release();
				}
				else if(proto.last.equals(Protocol.MESSAGE_TYPES.PLAYER_INFO))
				{
					player = proto.getPlayer();
					broadcast.release();
				}
				else if(proto.last.equals(Protocol.MESSAGE_TYPES.CHAT))
				{
					wasChat = true;
					chat = proto.getChat();
					broadcast.release();
				}
				else if(proto.last.equals(Protocol.MESSAGE_TYPES.READY) && !isGameInProgress() && !this.ready)
				{
					ServerController.ready = true;
					this.ready = true;
					readyCount++;
					broadcast.release();
				}
			}
			disconnect = true;
			if(!isGameInProgress() && this.ready)
				ServerController.readyCount--;
			dcdId = proto.getPlayerId();
			gl.removePlayer(proto.getPlayerId());
			clients.remove(this);
			proto.close();
			broadcast.release();
		}
	}

	private static void registerWithMaster()
	{
		URL location = null;
    	try {
    		location = new URL("http://hackergirlsliketoparty.com/connect/addserv.php?n=Connect%203%Server&p=" + PORT_NUMBER);
		} catch (MalformedURLException e) {
			e.printStackTrace();
			return;
		}
		HttpURLConnection list = null;
    	try {
			list = (HttpURLConnection) location.openConnection();
			list.setRequestMethod("GET");
			list.setDoInput(true);
			list.setDoOutput(false);
			list.setRequestMethod("GET");
			int resp = list.getResponseCode();
			if(resp != 200)
				throw new IOException("non-successful response recieved: " + resp);
    	} catch (IOException e) {

			System.err.println("Warning: failed to register with master server.");
			return;
		}
    	finally
    	{
    		list.disconnect();
    	}
	}

	//starts a new server process
	public static int startServer()
	{
		//load the configurationf ile

		config=new Properties();
		
		try {
			
			config.load(new FileInputStream("server.config"));
			//read values off config file, and set up accordingly
			
			
		} catch (Exception e1) {
			System.err.println("failed to load server configuration file: " + e1.getMessage());
			System.exit(1);
		}
		maxPlayers=Integer.parseInt(config.getProperty("numberOfPlayers"));
		gridWidth=Integer.parseInt(config.getProperty("gridWidth"));
		gridHeight=Integer.parseInt(config.getProperty("gridHeight"));
		playerTime=Integer.parseInt(config.getProperty("timeLimit"));
		PORT_NUMBER=Integer.parseInt(config.getProperty("port"));
		registerWithMaster = (Integer.parseInt(config.getProperty("register")) == 1);
		
		//register with master server
		if(registerWithMaster)
			registerWithMaster();
		
		//create the game board
		gb = new GameBoard(gridHeight, gridWidth);


		//create the server listen socket
		try {
			incoming = new ServerSocket(PORT_NUMBER);
		} catch (IOException e) {
			System.err.println("Failed to bind server socket to port " + PORT_NUMBER);
			return 1;
		}
		//start broadcast thread
		broadcast.acquireUninterruptibly();
		(new LogicThread()).start();

		//start accept loop
		(new ServerThread()).start();

		return 0;
	}

	public static synchronized boolean isGameInProgress()
	{
		return readyCount  >= gl.playerList.size() && gl.playerList.size() > 0;
	}
	
	public static void main(String[] args) {
		if(startServer() == 1)
			System.exit(1);
	}
}
